from itertools import chain
from django.db.models.fields import (
    DateTimeField,
    DateField,
    CharField,
    TextField,
    DecimalField,
    BooleanField,
)
from datetime import datetime
from django.db.models import Model, JSONField
import json

"""
封装django自带的将模型转成字典的方法，新增处理DateTimeField和DateField 
"""


def model_to_dict(instance, fields=None, exclude=None, replace=None, default=None):
    """
    :params instance: 模型对象，不能是queryset数据集
    :params fields: 指定要展示的字段数据，('字段1','字段2')
    :params exclude: 指定排除掉的字段数据,('字段1','字段2')
    :params replace: 将字段名字修改成需要的名字，{'数据库字段名':'前端展示名'}
    :params default: 新增不存在的字段数据，{'字段':'数据'}
    """
    # 对传递进来的模型对象校验
    if not isinstance(instance, Model):
        raise Exception("model_to_dict接收的参数必须是模型对象")
    # 对替换数据库字段名字校验
    if replace and type(replace) == dict:
        for replace_field in replace.values():
            if hasattr(instance, replace_field):
                raise Exception(f"model_to_dict,要替换成{replace_field}字段已经存在了")
    # 对要新增的默认值进行校验
    if default and type(default) == dict:
        for default_key in default.keys():
            if hasattr(instance, default_key):
                raise Exception(
                    f"model_to_dict,要新增默认值，但字段{default_key}已经存在了"
                )
    opts = instance._meta
    data = {}
    for f in chain(opts.concrete_fields, opts.private_fields, opts.many_to_many):
        # 源码下：这块代码会将时间字段剔除掉，我加上一层判断，让其不再剔除时间字段
        if not getattr(f, "editable", False):
            if type(f) == DateField or type(f) == DateTimeField:
                pass
            else:
                continue
        # 如果fields参数传递了，要进行判断
        if fields is not None and f.name not in fields:
            continue
        # 如果exclude 传递了，要进行判断
        if exclude and f.name in exclude:
            continue

        key = f.name
        # 获取字段对应的数据
        if type(f) == DateTimeField:
            # 字段类型是，DateTimeFiled 使用自己的方式操作
            value = getattr(instance, key)
            if value:
                value = datetime.strftime(value, "%Y-%m-%d %H:%M:%S")
            else:
                value = ""
        elif type(f) == DateField:
            # 字段类型是，DateFiled 使用自己的方式操作
            value = getattr(instance, key)
            if value:
                value = datetime.strftime(value, "%Y-%m-%d")
            else:
                value = ""
        elif type(f) == CharField or type(f) == TextField:
            # 字符串数据是否可以进行序列化，转成python结构数据
            value = getattr(instance, key)
            if value is not None:
                try:
                    value = json.loads(value)
                    if type(value) in [int, float]:
                        value = str(value)
                except Exception as _:
                    value = value
        elif type(f) == DecimalField:
            # 双精度的改成字符串
            value = getattr(instance, key)
            if value is not None:
                try:
                    value = str(value)
                except Exception as _:
                    value = value
        elif type(f) == BooleanField:
            # bool改成数值0和1
            value = getattr(instance, key)
            if value is not None:
                try:
                    value = int(value)
                except Exception as _:
                    value = value
        else:  # 其他类型的字段
            key = f.name
            value = f.value_from_object(instance)
        # 1、替换字段名字
        if replace and key in replace.keys():
            key = replace.get(key)
        data[key] = value
    # 2、新增默认的字段数据
    if default:
        data.update(default)
    return data


def model_to_list(data: list, fields=None, exclude=None, replace=None, default=None):
    """
    将queryset转成[{},{}] 格式
    :param data:
    :return:
    """
    all_list = []
    if len(data) == 0:
        return all_list
    for dic in data:
        try:
            all_list.append(
                model_to_dict(
                    dic,
                    fields=fields,
                    exclude=exclude,
                    replace=replace,
                    default=default,
                )
            )
        except Exception as e:
            raise ValueError(str(e))
    return all_list
